using System;
using System.Collections.Generic;
using System.Reflection;

using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>Provides all components required to perform search operations.</summary>
    public sealed class SearchProvider
    {
        #region SearchComponentInfo class.

        /// <summary>
        /// Contains information required to build a search component. Eg: ICommandDisplay component.
        /// </summary>
        sealed class SearchComponentInfo
        {
            #region Fields.

            string assembly;
            string className;
            IDictionary<string, object> settings;

            #endregion

            #region CTor.

            /// <summary>
            /// Initializes an instance of <b>SearchComponentInfo</b> class.
            /// </summary>
            public SearchComponentInfo(string assembly, string className, IDictionary<string, object> settings)
            {
                if (string.IsNullOrEmpty(assembly))
                    throw new ArgumentNullException("assembly");
                if (string.IsNullOrEmpty(className))
                    throw new ArgumentNullException("className");
                if (settings == null)
                    throw new ArgumentNullException("settings");

                this.assembly = assembly;
                this.className = className;
                this.settings = settings;
            }

            #endregion

            #region Getters.

            /// <summary>
            /// Gets the assembly string which containes the class that implements <see cref="ICommandDisplay"/> interface.
            /// </summary>
            public string Assembly
            {
                get { return this.assembly; }
            }

            /// <summary>
            /// Gets the class name of the <see cref="ICommandDisplay"/> implementation.
            /// </summary>
            public string Class
            {
                get { return this.className; }
            }

            /// <summary>
            /// Gets the display settings.
            /// </summary>
            public IDictionary<string, object> Settings
            {
                get { return this.settings; }
            }

            #endregion
        }

        #endregion
        
        #region Fields.

        string caption;        
        IDictionary<string, object> extendedProperties = new Dictionary<string, object>();
        bool filterDisplayed = true;
        
        string filterAssembly;
        string filterClass;
        IDictionary<string, object> filterSettings;

        string viewAssembly;
        string viewClass;
        IDictionary<string, object> viewSettings;

        List<SearchComponentInfo> commandDisplays = new List<SearchComponentInfo>();

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the <b>SearchProvider</b> class.
        /// </summary>
        public SearchProvider()
        { 
        }

        /// <summary>
        /// Initializes a new instance of the <b>SearchProvider</b> class.
        /// </summary>
        /// <param name="caption">Caption that is displayed to the user.</param>
        /// <param name="searchEngineBuilder">Object which builds the search engine consumed by the filter and view components.</param>
        /// <param name="searchFilterComponent">Component which provides values for the <see cref="ISearchEngine"/> object's filter.</param>
        /// <param name="searchViewComponent">Component that displays search results and provides custom actions for selected items.</param>
        public SearchProvider(string caption, ISearchEngineBuilder searchEngineBuilder, ISearchFilter searchFilterComponent, ISearchView searchViewComponent)
        {
            if (searchEngineBuilder == null)
                throw new ArgumentNullException("Search engine builder is required.", "searchEngineBuilder");

            this.caption = caption;

            this.EngineBuilderAssembly = searchEngineBuilder.GetType().Assembly.FullName;
            this.EngineBuilderClass = searchEngineBuilder.GetType().FullName;

            if (searchFilterComponent != null)
            {
                this.filterAssembly = searchFilterComponent.GetType().Assembly.FullName;
                this.filterClass = searchFilterComponent.GetType().FullName;
                this.filterSettings = searchFilterComponent.GetSettings();
            }

            if (searchViewComponent != null)
            {
                this.viewAssembly = searchViewComponent.GetType().Assembly.FullName;
                this.viewClass = searchViewComponent.GetType().FullName;
                this.viewSettings = searchViewComponent.GetSettings();
            }
        }

        #endregion

        #region Methods.

        /// <summary>
        /// Creates the object which builds the search engine consumed by the filter and view components.
        /// </summary>
        /// <returns>A new <see cref="BizElements.BusinessLayer.ISearchEngineBuilder"/> instance.</returns>
        public ISearchEngineBuilder CreateEngineBuilderInstance()
        {
            return (ISearchEngineBuilder)Assembly.Load(this.EngineBuilderAssembly).CreateInstance(this.EngineBuilderClass);
        }

        /// <summary>
        /// Creates a filter component defined by the <see cref="FilterAssembly"/> and <see cref="FilterClass"/> properties.
        /// </summary>
        /// <returns>A new <see cref="ISearchFilter"/> instance.</returns>
        public ISearchFilter CreateFilterInstance()
        {
            ISearchFilter filter = (ISearchFilter)Assembly.Load(this.FilterAssembly).CreateInstance(this.FilterClass);
            filter.ApplySettings(this.FilterSettings);
            return filter;
        }

        /// <summary>
        /// Creates a view component defined by the <see cref="ViewAssembly"/> and <see cref="ViewClass"/> properties.
        /// </summary>
        /// <returns>A new <see cref="ISearchView"/> instance.</returns>
        public ISearchView CreateViewInstance()
        {
            ISearchView view = (ISearchView)Assembly.Load(this.ViewAssembly).CreateInstance(this.ViewClass);
            view.ApplySettings(this.ViewSettings);
            return view;
        }

        /// <summary>
        /// Creates a collection of <see cref="ICommandDisplay"/> components which display the commands provided
        /// by the <see cref="ISearchView"/> component in UI.
        /// </summary>
        /// <returns>ICommandDisplay components.</returns>
        public IList<ICommandDisplay> CreateCommandDisplayInstances()
        {
            return CreateCommandDisplayInstances(true);
        }

        /// <summary>
        /// Creates a collection of <see cref="ICommandDisplay"/> components which display the commands provided
        /// by the <see cref="ISearchView"/> component in UI.
        /// </summary>
        /// <param name="applySettings">Indicates whether the stored settings should be applied to created instances.</param>
        /// <returns>ICommandDisplay components.</returns>
        private IList<ICommandDisplay> CreateCommandDisplayInstances(bool applySettings)
        {
            List<ICommandDisplay> instances = new List<ICommandDisplay>();
            foreach (SearchComponentInfo info in this.commandDisplays)
            {
                ICommandDisplay newInstance = (ICommandDisplay)Assembly.Load(info.Assembly).CreateInstance(info.Class);
                if (applySettings)
                    newInstance.ApplySettings(info.Settings);

                instances.Add(newInstance);
            }

            return instances;
        }

        /// <summary>
        /// Gets a collection of settings for <see cref="ICommandDisplay"/> components.
        /// </summary>
        /// <returns>A collection ordered in the same fashion as a collection of display components
        /// created by the <see cref="CreateCommandDisplayInstances()"/> method.</returns>
        private IList<IDictionary<string, object>> GetDisplaySettingsCollection()
        {
            List<IDictionary<string, object>> settings = new List<IDictionary<string, object>>();
            foreach (SearchComponentInfo info in this.commandDisplays)
                settings.Add(info.Settings);

            return settings;
        }

        /// <summary>
        /// Adds a <see cref="ICommandDisplay"/> component which displays all or some of the commands
        /// provided by the <see cref="ISearchView"/> component in UI. 
        /// </summary>
        /// <param name="display">Display component.</param>
        public void AddCommandDisplay(ICommandDisplay display)
        {
            Type type = display.GetType();
            AddCommandDisplay(type.Assembly.FullName, type.FullName, display.GetSettings());
        }

        /// <summary>
        /// Adds a <see cref="ICommandDisplay"/> component which displays all or some of the commands
        /// provided by the <see cref="ISearchView"/> component in UI. 
        /// </summary>
        /// <param name="displayAssembly">The <b>assemblyString</b> of the <see cref="Assembly"/> which contains 
        /// the <see cref="ICommandDisplay"/> class specified in the <b>displayClass</b> parameter.</param>
        /// <param name="displayClass">The <b>typeName</b> of the <see cref="ICommandDisplay"/> class.</param>
        /// <param name="displaySettings">The settings for the <see cref="ICommandDisplay"/> class 
        /// specified in the <b>displayClass</b> parameter.</param>
        private void AddCommandDisplay(string displayAssembly, string displayClass, IDictionary<string, object> displaySettings)
        {
            SearchComponentInfo display = new SearchComponentInfo(displayAssembly, displayClass, displaySettings);
            this.commandDisplays.Add(display);
        }

        /// <summary>
        /// Gets the value associated with the specified key in the <see cref="ExtendedProperties"/>.
        /// </summary>
        /// <param name="key">The key whose value to get.</param>
        /// <returns>When this method returns, the value associated with the specified key, if the key is found; otherwise, <b>null</b> is returned.</returns>
        public object TryGetExtendedProperty(string key)
        {
            object value;
            this.ExtendedProperties.TryGetValue(key, out value);
            return value;
        }

        #endregion

        #region Properties.

        /// <summary>Gets or sets the <b>assemblyString</b> of the <see cref="Assembly"/> which contains the <see cref="BizElements.BusinessLayer.ISearchEngineBuilder"/> class provided by the current SearchProvider instance.</summary>
        public string EngineBuilderAssembly { get; set; }

        /// <summary>Gets or sets the <b>typeName</b> of the <see cref="BizElements.BusinessLayer.ISearchEngineBuilder"/> class provided by the current SearchProvider instance.</summary>
        public string EngineBuilderClass { get; set; }

        /// <summary>
        /// Gets or sets the caption that is displayed to the user.
        /// </summary>
        public string Caption
        {
            get { return this.caption; }
            set { this.caption = value; }
        }

        /// <summary>
        /// Gets or sets whether the filter component should be initially displayed in UI.
        /// </summary>
        /// <value><b>true</b> if filter should be displayed; <b>false</b> otherwise. Default is <b>true</b>.</value>
        public bool FilterDisplayed
        {
            get { return this.filterDisplayed; }
            set { this.filterDisplayed = value; }
        }

        /// <summary>
        /// Gets or sets whether quick filter commands will be automatically generated.
        /// </summary>
        /// <value><b>true</b> to generate quick filter; <b>false</b> otherwise. Default is <b>false</b>.</value>
        public bool QuickFilterEnabled { get; set; }

        /// <summary>
        /// Gets or sets the caption for quick filter section.
        /// </summary>
        public string QuickFilterCaption { get; set; }        

        /// <summary>
        /// Gets or sets the collection of customized user information. 
        /// </summary>
        public IDictionary<string, object> ExtendedProperties
        {
            get { return this.extendedProperties; }
            set { this.extendedProperties = value; }
        }

        /// <summary>
        /// Gets or sets the <b>assemblyString</b> of the <see cref="Assembly"/> which contains the 
        /// <see cref="ISearchFilter"/> class provided by the current SearchProvider instance.
        /// </summary>
        public string FilterAssembly
        {
            get { return this.filterAssembly; }
            set { this.filterAssembly = value; }
        }

        /// <summary>
        /// Gets or sets the <b>typeName</b> of the <see cref="ISearchFilter"/> class 
        /// provided by the current SearchProvider instance.
        /// </summary>
        public string FilterClass
        {
            get { return this.filterClass; }
            set { this.filterClass = value; }
        }

        /// <summary>
        /// Gets or sets the settings for the <see cref="ISearchFilter"/> class 
        /// provided by the current SearchProvider instance. 
        /// </summary>
        public IDictionary<string, object> FilterSettings
        {
            get { return this.filterSettings; }
            set { this.filterSettings = value; }
        }

        /// <summary>
        /// Gets or sets the <b>assemblyString</b> of the <see cref="Assembly"/> which contains the 
        /// <see cref="ISearchView"/> class provided by the current SearchProvider instance.
        /// </summary>
        public string ViewAssembly
        {
            get { return this.viewAssembly; }
            set { this.viewAssembly = value; }
        }

        /// <summary>
        /// Gets or sets the <b>typeName</b> of the <see cref="ISearchView"/> class 
        /// provided by the current SearchProvider instance.
        /// </summary>
        public string ViewClass
        {
            get { return this.viewClass; }
            set { this.viewClass = value; }
        }

        /// <summary>
        /// Gets or sets the settings for the <see cref="ISearchView"/> class 
        /// provided by the current SearchProvider instance. 
        /// </summary>
        public IDictionary<string, object> ViewSettings
        {
            get { return this.viewSettings; }
            set { this.viewSettings = value; }
        }

        #endregion
    }
}
